<template>
  <div class="search-box">
    <input type="text" id="searchBox" class="search-txt" placeholder="姓名?" @input="handleSearch" />
    <a class="search-btn">
      <i class="fa fa-search" aria-hidden="true"></i>
      <!-- 引入FontAwesome的CSS -->
      <link href="https://cdn.bootcdn.net/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
    </a>
  </div>
  <div class="button">
    <!-- <img id="editBtn" src="/src/assets/刷新.jpeg"> -->
  </div>
  <div id="relation" class="network"></div>

</template>

<script>
import * as d3 from 'd3';

export default {
  name: "Relation",
  data() {
    return {
      dataUrl: '/json/杜甫人物关系图谱.json',
      simulation: null,//存储D3力模拟的实例
      defaultConfig: {//默认配置
        data: { nodes: [], links: [] },
        width: 1500,
        height: 850,
        distance: 150
           },
      links: [],
      linetext: [],
      tooltip: null,
      searchQuery: '',
      dependsNode: [],//存储当前高亮显示的节点索引的数组
      dependsLinkAndText: [],//存储当前高亮显示的链接和文本标签索引的数组
      nodeMouseoutTimeout: null, // 用于存储鼠标移出的定时器
    };
  },
  mounted() {
    this.createSvg();
    this.loadData();
    this.initSearchBox();
    this.initTooltip(); // 初始化悬浮框
  },
  methods: {
    initTooltip() {
      this.tooltip = d3.select(".network").append("div")
        .attr("class", "tooltip")//使用 D3.js 的 attr 方法设置新 div 的类名为 tooltip
        .attr("opacity", 0.0)//设置 div 的透明度为 0.0，这样在开始时悬浮提示框是不可见的
        .on('dblclick', function () {
          d3.event.stopPropagation();//在双击事件发生时，调用 d3.event.stopPropagation() 阻止事件冒泡
        })
        .on('mouseover', function () {//鼠标移入事件处理器
          if (this.nodeMouseoutTimeout) {
            clearTimeout(this.nodeMouseoutTimeout);
            this.nodeMouseoutTimeout = null;
          }
        })
        .on('mouseout', function () {//鼠标移出事件处理器
          if (this.nodeMouseoutTimeout) {
            clearTimeout(this.nodeMouseoutTimeout);
            this.nodeMouseoutTimeout = null;
          }
          this.nodeMouseoutTimeout = setTimeout(() => {
           // this.highlightToolTip(null,null);
          }, 100);
        });
    },
    highlightToolTip(obj, event) {//obj 是当前鼠标悬停的节点对象，event 是鼠标事件对象。
      if (obj) {
        // 使用事件对象的layerX和layerY属性获取鼠标位置
        const mouseX = event.layerX;
        const mouseY = event.layerY;

        this.tooltip.html("<div class='title'>" + obj.name + "的详情</div>" +
          "<table class='detail-info'>" +
          "<tr><td class='td-label'>字号：</td><td>" + obj.zihao + "</td></tr>" +
          "<tr><td class='td-label'>朝代：</td><td>" + obj.dynasty + "</td></tr>" +
          "<tr><td class='td-label'>年龄：</td><td>" + obj.age + "</td></tr>" +
          "<tr><td class='td-label'>称号：</td><td>" + obj.epithet + "</td></tr>" +
          "<tr><td class='td-label'>事迹：</td><td>" + obj.deeds + "</td></tr>" +
          "<tr><td class='td-label'>代表作：</td><td>" + obj.famous + "</td></tr>" +
          "<tr><td class='td-label'>链接：</td><td><a href='" + obj.link + "'>" + obj.name + "的主页</a></td></tr>" +
          "</table>")
          .style("left", (mouseX + 20) + "px")
          .style("top", (mouseY - 20) + "px")
          .style("opacity", 1.0);
      } else {
        this.tooltip.style("opacity", 0.0);
      }
    },
    initSearchBox() {
      const searchBox = document.getElementById('searchBox');
      searchBox.addEventListener('input', () => {
        this.searchQuery = searchBox.value.trim();//首先通过searchBox.value.trim()获取搜索框的当前值，并去除首尾空格，然后将这个值赋给this.searchQuery
        this.highlightSearchResult();
      });
    },
    highlightSearchResult() {
      this.defaultConfig.data.nodes.forEach(node => {
        node.highlighted = false;
      });
      this.links.classed('highlight', false).classed('inactive', false);
      this.linetext.classed('highlight', false).classed('inactive', false);
      this.node.classed('highlight', false).classed('inactive', false);
      this.dependsLinkAndText = [];
      if (this.searchQuery) {
        // 节点姓名匹配
        const matchedNode = this.defaultConfig.data.nodes.find(node =>
          node.name.toLowerCase().includes(this.searchQuery.toLowerCase())
        );
        if (matchedNode) {
          matchedNode.highlighted = true;
          this.highlightObject(matchedNode);
        }
      } else {
        this.defaultConfig.data.nodes.forEach(node => {
          node.fx = null;
          node.fy = null;
          node.highlighted = false;
        });
        //this.updateNetwork();// 当搜索框为空时，重新渲染网络
      }
    },
    highlightObject(obj) {
      const links = this.defaultConfig.data.links;
      const nodes = this.defaultConfig.data.nodes;
      let dependsNode = new Set();
      let dependsLinkAndText = new Set();

      if (obj) {
        dependsNode.add(obj.index);
        dependsLinkAndText.add(obj.index);

        links.forEach(link => {
          if (link.source.index === obj.index || link.target.index === obj.index) {
            dependsNode.add(link.source.index);
            dependsNode.add(link.target.index);
            dependsLinkAndText.add(link.index);
          }
        });

        this.node.classed('inactive', d => !dependsNode.has(d.index));
        this.links.classed('inactive', d => !dependsLinkAndText.has(d.index));
        this.linetext.classed('inactive', d => !dependsLinkAndText.has(d.index));
      } else {
        this.node.classed('inactive', false);
        this.links.classed('inactive', false);
        this.linetext.classed('inactive', false);
      }
    },

    transformData(data) {
      // 检查是否存在名字，进行索引转换
      data.links.forEach((link) => {
        if (typeof link.source !== "number" && typeof link.target !== "number") {
          const sourceNode = data.nodes.filter(node => node.name === link.source)[0];
          const targetNode = data.nodes.filter(node => node.name === link.target)[0];
          link.source = sourceNode ? sourceNode.name : null;
          link.target = targetNode ? targetNode.name : null;
        }
      });
    },
    loadData() {
      fetch(this.dataUrl) 
        .then(response => {
          if (!response.ok) {
            throw new Error('网络响应错误: ' + response.status + ' ' + response.statusText);
          }
          return response.json();
        })
        .then(data => {
          //console.log(data); // 查看 JSON 数据  
          data.nodes.forEach((node, index) => {
            // 使用随机位置或者基于某种逻辑的位置
            node.x = this.defaultConfig.width / 2 + Math.cos(index / data.nodes.length * 2 * Math.PI) * 100;
            node.y = this.defaultConfig.height / 2 + Math.sin(index / data.nodes.length * 2 * Math.PI) * 100;
          });

         
          this.transformData(data);
          this.defaultConfig.data = data; // 更新数据  
          this.createSimulation(); // 创建模拟器  
          this.updateNetwork(); // 更新网络  
        })
        .catch(error => {
          console.error('获取 JSON 数据时出错:', error);
        });
    },
    createSvg() {
      const svg = d3.select('.network').append('svg')
        .attr('width', this.defaultConfig.width)
        .attr('height', this.defaultConfig.height);
      // 设置缩放范围和缩放响应函数。
      const zoomHandler = d3.zoom()
        .scaleExtent([0.2, 8])//缩放比例
        .on('zoom', (event) => {
          svg.selectAll('.all').attr('transform', event.transform);
        });

      this.vis = svg.append('g').attr('class', 'all');
      svg.call(zoomHandler).on("dblclick.zoom", null);// 实现双击还原
    },
    createSimulation() {
      this.simulation = d3.forceSimulation(this.defaultConfig.data.nodes)
        //引力
        .force('link', d3.forceLink().id(d => d.name).distance(this.defaultConfig.distance)) // 使用 name 作为唯一标识  
        //斥力
        .force('charge', d3.forceManyBody().strength(-100))
        // 设置可视化中心。
        .force('center', d3.forceCenter(this.defaultConfig.width / 2, this.defaultConfig.height / 2))
        // 将碰撞力指定为 ​"collide"​ 类型的力，使节点保持至少 ​60​ 个单位的距离，碰撞的强度为 0.2，使节点之间有一定的距离。​
        // .iterations(5)​ 设置迭代次数为 5，提高碰撞的精确度。
        .force("collide", d3.forceCollide(70).strength(0.4).iterations(5))
        .alphaTarget(1)
        .on('tick', () => this.tick()); // 在每次 tick 时更新网络  
    },
    updateNetwork() {
      // 创建箭头​<marker>​元素，之后可以使用该箭头标记在连接线的末端添加箭头效果
      this.vis.append("svg:defs").selectAll("marker")
        .data(["end"])
        .enter().append("svg:marker")
        .attr("id", "arrow")
        .attr('class', 'arrow')
        // 定义了在通过视图呈现时，元素起始点和围绕元素的盒子的属性
        .attr("viewBox", "0 -5 10 10")
        // 在X轴、Y轴方向上的参考点
        .attr("refX", 42)
        .attr("refY", 0)
        // 宽高
        .attr("markerWidth", 10)
        .attr("markerHeight", 15)
        .attr("markerUnits", "userSpaceOnUse")
        .attr("orient", "auto")
        // 添加一个 ​<path>​ 元素，用于绘制箭头的路径
        .append("svg:path")
        // 设置路径命令
        .attr("d", "M0,-5L10,0L0,5")
        .attr('fill', '#666');
       

      // 更新链接
      this.links = this.vis.selectAll("line.link")
        .data(this.defaultConfig.data.links)
        .join("line")
        .attr("class", "link")
        .attr('stroke-width', 1.8)
        // 获取起始点、目标点x,y
        .attr("x1", d => d.source.x)
        .attr("y1", d => d.source.y)
        .attr("x2", d => d.target.x)
        .attr("y2", d => d.target.y)
        .attr("stroke", d => d.value === "好友" ? "#007175" : "gray")
        .attr("marker-end", "url(#arrow)");

      // 更新节点  
    
      this.node = this.vis.selectAll(".node")
        .data(this.defaultConfig.data.nodes, d => d.name)
        .enter().append("g")
        .attr("class", "node")
        .attr("transform", d => `translate(${d.x || 0}, ${d.y || 0})`)
        .on("mouseover", (event, d) => this.highlightToolTip(d, event)) // 传递事件对象
        .on("mouseout", (event, d) => this.highlightToolTip(null, event)) 
         //.call(this.createDrag())
        .on('start', (event, d) => {
          if (!event.active) this.simulation.alphaTarget(0.3).restart();
          d.fx = d.x;
          d.fy = d.y;
        })
        .on('drag', (event, d) => {
          d.fx = event.x;
          d.fy = event.y;
        })
        .on('end', (event, d) => {
          if (!event.active) this.simulation.alphaTarget(0);
          d.fx = null;
          d.fy = null;
        })
        ;

      this.node.append("text")
        .attr("dy", 4)
        .attr("x", 12)
        .text(d => d.name);

      this.node.append("svg:image")
        .attr("xlink:href", d => `/picture/${d.name}.jpg`) // 确保路径正确
        .attr("x", "-15px") // 图片位置，根据需要调整
        .attr("y", "-15px") // 图片位置，根据需要调整
        .attr("width", "30px") // 图片大小，根据需要调整
        .attr("height", "30px"); // 图片大小，根据需要调整
      // 更新节点和链接  
      this.simulation.nodes(this.defaultConfig.data.nodes);
      this.simulation.force('link').links(this.defaultConfig.data.links);
      // 为每条连线添加文本元素表示连线的关系描述
      this.linetext = this.vis.selectAll('.linetext')
        .data(this.defaultConfig.data.links)
        .join("text")
        .attr("class", "linetext")
        .attr("x", d => (d.source.x + d.target.x) / 2)
        .attr("y", d => (d.source.y + d.target.y) / 2 +1)
        .text(d => d.value)
        // .call(d3.drag())// 添加拖拽行为，使文本可以被拖动
        .style("font-family", "KaiTi, serif")
        
        .style("font-size", "16px")
        .style("fill", d => {
        if (d.value === "好友") {
        return "#007175";
        } else if (d.value === "儿子") {
        return"rgb(239, 122, 130)";
       } 
       else if (d.value === "父亲") {
        return"rgb(201, 55, 86)";}
      else if (d.value === "祖父") {
        return"rgb(203, 58, 86)";}
        else {
        return "black"; // 默认颜色
    }
  });
        

      this.simulation.alpha(0.5).restart();
     
    },
    dragstarted(event, d) {
      this.simulation.stop();
      d.fx = d.x;
      d.fy = d.y;
      event.sourceEvent.stopPropagation();
    },
    dragged(event, d) {
      d.fx = event.x; // 更新节点的拖拽目标位置
      d.fy = event.y;
      this.tick(); // 更新网络视图
    },
    dragended(event, d) {
      d.fx = null; // 取消固定位置
      d.fy = null;
      this.simulation.restart(); // 重新启动力模拟
    },
    createDrag() {
      return d3.drag()
        .on("start", this.dragstarted)
        .on("drag", this.dragged)
        .on("end", this.dragended);
    },
    tick() {
      // 更新力模拟的 tick 事件
      this.links.attr("x1", d => d.source.x)
        .attr("y1", d => d.source.y)
        .attr("x2", d => d.target.x)
        .attr("y2", d => d.target.y);

      this.linetext.attr("x", d => (d.source.x + d.target.x) / 2)
        .attr("y", d => (d.source.y + d.target.y) / 2);

      this.node.attr("transform", d => `translate(${d.x},${d.y})`);
      
    }
  },
  computed: {
    nodeDrag() {
      return this.createDrag();
    }
  }
}  
</script>

<style>
#relation {
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
  font-family: 'Arial', sans-serif;
}
.link.inactive,
.linetext.inactive,
.node.inactive image,
.node.inactive text {
  opacity: .2;
}


.tooltip {
  font-family: "KaiTi", "serif";
  font-size: 12px;
  width: 220px;
  height: auto;
  position: absolute;
  background: #fff;
  opacity: 0.5;
  border-radius: 5px;
  box-shadow: 0 5px 10px rgba(0, 0, 0, .2);
}

.tooltip .title {
  color: #fff;
  padding: 5px;
  font-size: 14px;
  background-color: #d2a36c;
  border-radius: 5px 5px 0 0;
}

.tooltip .detail-info {
  width: 100%;
  border-collapse: collapse;
  border: 1px solid #d2a36c;
}

.tooltip .detail-info td {
  padding: 3px 5px;
  color: #666;
  vertical-align: middle;
}

.tooltip .detail-info tr:nth-of-type(odd) {
  background: #f9f9f9;
}

.tooltip .detail-info td.td-label {
  color: #333;
  width: 60px;
}

.tooltip .detail-info td a {
  color: #666;
}
.search-box {
  position: absolute;
  left: 12%;
  top: 15%;
  transform: translate(-50%, -50%);
  background-color: #a04c3b;
  height: 30px;
  margin-top: 20px;
  padding: 10px;
  border-radius: 40px;
}

.search-txt {
  border: none;
  background: none;
  outline: none;
  float: left;
  padding: 0;
  color: #fff;
  font: 16px sans-serif;
  line-height: 30px;
  width: 0;
  /* 动画过渡 */
  transition: 0.4s;
}

.search-txt::placeholder {
  color: #ffffff67;
}

.search-btn {
  color: #fff;
  float: right;
  width: 3cap;
  height: 30px;
  border-radius: 50%;
  background-color: #a04c3b;
  /* 弹性布局 水平垂直居中 */
  display: flex;
  justify-content: center;
  align-items: center;
  cursor: pointer;
  /* 动画过渡 */
  transition: 0.4s;
}

.search-box:hover .search-txt {
  width: 200px;
  padding: 0px 10px;
}

.search-box:hover .search-btn {
  background-color: #a04c3b;
}
 .node.highlight circle {
   fill: red;
 }

 .node.highlight text {
   fill: red;
 }

 .link.highlight {
   stroke: red;
 }

 .linetext.highlight {
   fill: red;
 }
 .button {
   position: absolute;
   left: 90%;
   top: 5%;
 }

 #editBtn {
  margin-top: 80px;
  margin-right: 2px;
   height: 30px;
   width: 30px;
 }

 #editBtn:hover {
   scale: 1.2;
 }
 .network{
  width:100%;
  height: 100%;
 }
 
</style>